﻿using System;
using System.IO;
using System.Net;
using IEx.Common;
using IEx.Common.Sources;
using IEx.Model.Partials;
using IEx.Model.Partials.Media;
using IEx.Utilities;

namespace IEx.Downloader
{
    public class FileDownloader
    {
        private const byte REDIRECTION_LIMIT = 1;
        public WebRequestRetryPolicy retry;
        public event EventHandler<ProgressEventArgs> DownloadProgressChanged;

        public FileDownloader()
        {
            retry = new WebRequestRetryPolicy();
        }

        public static string DownloadedFolderPath
        {
            get
            {
                return DataFeedConfiguration.GetConfig().DownloadedFolder.Path;
            }
        }

        /// <summary>
        /// Download a file from remote site and keeping original file name
        /// </summary>
        /// <param name="url"></param>
        /// <param name="fileName"></param>
        /// <param name="directory"></param>
        void FriendlyDownloadFile(string url, ref string fileName, string directory, Action<bool> onDownloadSuccess, byte redirectionLimit = 0)
        {
            System.Net.HttpWebRequest request = null;
            System.Net.WebResponse response = null;
            try
            {
                request = System.Net.WebRequest.Create(url) as System.Net.HttpWebRequest;
                request.AllowAutoRedirect = false; // Turn-off AutoRedirect to detect download file name
                request.UserAgent = "Mozilla/4.0 (Compatible; Windows NT 5.1; MSIE 6.0) (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)";
                response = request.GetResponse();

                string contentType = (response.Headers["Content-Type"] ?? "").ToLower();
                string location = response.Headers["Location"];

                // Detect HTTP redirect code. Will redirect to actual file's location
                if (redirectionLimit < REDIRECTION_LIMIT
                    && !string.IsNullOrEmpty(location)
                    && (string.IsNullOrEmpty(contentType) || contentType.IndexOf("text/html") >= 0))
                {
                    FriendlyDownloadFile(location, ref fileName, directory, onDownloadSuccess, ++redirectionLimit);
                    return;
                }
                else //if (contentType.IndexOf("application/pdf") >= 0)
                {
                    if (string.IsNullOrEmpty(fileName))
                    {
                        string contentDisposition = response.Headers["Content-Disposition"] ?? "";
                        if (string.IsNullOrEmpty(contentDisposition))
                        {
                            fileName = Path.GetFileName(request.RequestUri.LocalPath.TrimStart('/'));
                        }
                        else
                        {
                            string lookFor = "filename=";
                            int index = contentDisposition.IndexOf(lookFor, StringComparison.CurrentCultureIgnoreCase);
                            if (index >= 0)
                                fileName = contentDisposition.Substring(index + lookFor.Length);
                        }
                    }
                }

                if (!string.IsNullOrEmpty(fileName))
                {
                    fileName = Path.Combine(directory, fileName);
                    bool fileExists = true;
                    if (!File.Exists(fileName))
                    {
                        fileExists = false;
                        // Begin get bytes of remote content
                        byte[] buffer = new byte[4096];

                        // Always create new or overwriten if file already exists
                        using (Stream responseStream = response.GetResponseStream())
                        {
                            using (FileStream fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                            {
                                int count = 0;
                                do
                                {
                                    count = responseStream.Read(buffer, 0, buffer.Length);
                                    fileStream.Write(buffer, 0, count);
                                } while (count != 0);
                            }
                        }
                    }

                    if (onDownloadSuccess != null)
                        onDownloadSuccess.Invoke(fileExists);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (response != null)
                    response.Close();
            }
        }

        /// <summary>
        /// Begin download a file
        /// </summary>
        /// <param name="announcement">Downloaded file's information</param>
        /// <returns>Physical path of the downloaded file</returns>
        public string Download(Announcement announcement, string fileDownloadUrl, ref HttpStatusCode status)
        {
            string fileName = string.Empty;
            string logInfo = string.Format("Download (cCode: {0}): {1}->", announcement.SourceCode, fileDownloadUrl);
            try
            {

                string date = DateTime.Now.Year + "-" + DateTime.Now.Date.Month.ToString("00") + "-" + DateTime.Now.Date.Day.ToString("00") + "-" + DateTime.Now.Date.Hour.ToString("00");
                string folder = Utility.GetSourceCodeFolderName(announcement.SourceId);
                string directoryPath = (announcement.SourceId == (int)SourceIds.Tamtay 
                    || announcement.SourceId == (int)SourceIds.Chotot 
                    || announcement.SourceId == (int)SourceIds.Rongbay) 
                    ? Path.Combine(FileDownloader.DownloadedFolderPath, announcement.SourceCode, folder, MiscUtility.ProcessString(UnicodeUtility.UnicodeToNoSign(announcement.Title)), date) 
                    : Path.Combine(FileDownloader.DownloadedFolderPath, announcement.SourceCode, folder, date);
                if (!Directory.Exists(directoryPath))
                    Directory.CreateDirectory(directoryPath);

                // Ensure there's available file name
                //if (!string.IsNullOrEmpty(announcement.PhysicalFileName))
                //    fileName = announcement.PhysicalFileName;

                // Keeping the file's name which returned from remote host to avoid duplicate
                // file's name for many news in a day.
                this.FriendlyDownloadFile(fileDownloadUrl, ref fileName, directoryPath, (fileExists) =>
                {
                    logInfo += fileExists ? " File exists!" : " Done!";
                    Logger.Debug(logInfo);
                });

            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError &&
                    ex.Response != null)
                {
                    var resp = (HttpWebResponse)ex.Response;
                    status = resp.StatusCode;
                }

                if (ex.Status == WebExceptionStatus.Timeout)
                {
                    status = HttpStatusCode.RequestTimeout;
                }

                //if (ex.InnerException != null && ex.InnerException.Message.Contains("used by another process")){}

                // Write to log the Fail status for downloading
                fileName = string.Empty;
                string message = String.Format("Download (cCode: {0}): {1}", announcement.SourceCode, fileDownloadUrl);
                Logger.WriteError(message, ex);
                logInfo += " Fail!";
                Logger.Debug(logInfo);
                Logger.Debug(message + "\r\n" + ex.ToString());
            }
            catch (Exception ex)
            {
                // Write to log the Fail status for downloading
                fileName = string.Empty;
                string message = String.Format("Download (cCode: {0}): {1}", announcement.SourceCode, fileDownloadUrl);
                Logger.WriteError(message, ex);
                logInfo += " Fail!";
                Logger.Debug(logInfo);
                Logger.Debug(message + "\r\n" + ex.ToString());
            }


            return fileName;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="announcement"></param>
        /// <param name="fileName"></param>
        /// <param name="video"></param>
        /// <param name="directory"></param>
        public string Download(Announcement announcement, string fileName, string fileDownloadUrl, string directory)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(fileDownloadUrl);
            fileName = Path.Combine(directory, fileName);

            if (!File.Exists(fileName))
            {
                // the following code is alternative, you may implement the function after your needs
                using (WebResponse response = request.GetResponse())
                {
                    using (Stream source = response.GetResponseStream())
                    {
                        using (FileStream target = new FileStream(fileName, FileMode.Create, FileAccess.Write))
                        {
                            var buffer = new byte[1024];
                            bool cancel = false;
                            int bytes;
                            int copiedBytes = 0;
                            while (!cancel && (bytes = source.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                target.Write(buffer, 0, bytes);
                                copiedBytes += bytes;
                                var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100);

                                if (this.DownloadProgressChanged != null)
                                {
                                    this.DownloadProgressChanged(this, eventArgs);

                                    if (eventArgs.Cancel)
                                    {
                                        cancel = true;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return fileName;
        }
    }
}
